package com.polytech.diploma.Builder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.Tree;

import com.polytech.diploma.OracleSQL.PLSQLParser;
import com.polytech.diploma.SQLGenerator.Oracle.Node;
import com.polytech.diploma.SQLGenerator.Oracle.Arguments.Argument;
import com.polytech.diploma.SQLGenerator.Oracle.Arguments.ArgumentSet;
import com.polytech.diploma.SQLGenerator.Oracle.Arguments.Constant;
import com.polytech.diploma.SQLGenerator.Oracle.Arguments.DataType;
import com.polytech.diploma.SQLGenerator.Oracle.Arguments.Field;
import com.polytech.diploma.SQLGenerator.Oracle.Conditions.Condition;
import com.polytech.diploma.SQLGenerator.Oracle.Conditions.ConditionArgument;
import com.polytech.diploma.SQLGenerator.Oracle.Conditions.ConditionType;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.CaseExpression;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.Function;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.FunctionType;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.AnalyticFunctions.AnalyticFunction;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.AnalyticFunctions.WindowClause.ResultUnit;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.AnalyticFunctions.WindowClause.WindowBound;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.AnalyticFunctions.WindowClause.WindowBoundType;
import com.polytech.diploma.SQLGenerator.Oracle.Functions.AnalyticFunctions.WindowClause.WindowClause;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.HierarchicalClause;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.JoinStatement;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.JoinType;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.Subquery;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.Table;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.OrderByClause.OrderByClause;
import com.polytech.diploma.SQLGenerator.Oracle.Statements.OrderByClause.OrderByType;
import com.polytech.diploma.Walker.Token;

public class Builder {
	
	public Node build()
	{
		return null;
	}
	
	public void buildNode(Token token, CommonTree node)
	{
		
	}
	
	public Node buildTableRefElem(CommonTree TableRefElement)
	{
		CommonTree tableExpr = (CommonTree) TableRefElement.getFirstChildWithType(PLSQLParser.TABLE_EXPRESSION);
		CommonTree alias = (CommonTree) TableRefElement.getFirstChildWithType(PLSQLParser.ALIAS);
		
		/**
		 * Check mode:
		 * Direct mode - table name
		 * Select mode - subquery
		 */
		
		CommonTree mode = (CommonTree)tableExpr.getChild(0);
		switch(mode.getType())
		{
			case PLSQLParser.DIRECT_MODE:
			{
				CommonTree tableName = (CommonTree)mode.getFirstChildWithType(PLSQLParser.TABLEVIEW_NAME);
				deleteChild(mode, tableName);
				return buildTable(tableName, alias);
			}				
			case PLSQLParser.SELECT_MODE:
			{
				CommonTree subquery = (CommonTree)mode.getFirstChildWithType(PLSQLParser.SUBQUERY); 
				deleteChild(mode, subquery);
				return buildSubquery(subquery, alias);
			}
			default:
				return null;
		}			
	}
	
	public Node buildTable (CommonTree tableName, CommonTree alias)
	{
		Table table = new Table.Builder()
									.setAlias(alias.toString())
									.setName(tableName.toString())
									.build();
		
		return table;
	}
	
	public Node buildSubquery(CommonTree subquery, CommonTree alias)
	{
		/**
		 * TODO: Build subquery
		 */
		//Node MP = buildFromClause(fromClause);
		return null;
	}
	
	public Node buildFromClause(CommonTree fromClause)
	{
		List<CommonTree> tableRefs = fromClause.getChildren();		
		return buildJoinTree(tableRefs.iterator());
	}
	
	public Table buildJoinTree(Iterator<CommonTree> tableRefIter)
	{
		CommonTree tableRef = tableRefIter.next();
		if(tableRefIter.hasNext())
		{
			Table.Builder tableBuilder = new Table.Builder();
			JoinStatement.Builder joinBuilder = new JoinStatement.Builder();
			joinBuilder.setLeftTable((Table)buildTableRef(tableRef));
			joinBuilder.setRightTable(buildJoinTree(tableRefIter));
			tableBuilder.setSubquery(joinBuilder.build());
			return tableBuilder.build();			
		}
		else
		{
			return (Table) buildTableRef(tableRef);
		}
			
	}
	
	public Node buildTableRef(CommonTree tableRef)
	{
		/**
		 * Build first table entry
		 */
		CommonTree tableRefElem = (CommonTree) tableRef.getFirstChildWithType(PLSQLParser.TABLE_REF_ELEMENT);
		Node leftTable = buildTableRefElem(tableRefElem);
		tableRef = deleteChild(tableRef, tableRefElem);
		
		CommonTree joinClause = (CommonTree)tableRef.getFirstChildWithType(PLSQLParser.JOIN_DEF);
		if(joinClause == null)
			return leftTable;
		
		Subquery joinSubquery = (Subquery) buildJoinClause(joinClause, leftTable);
		return new Table.Builder()
							.setSubquery(joinSubquery)
							.setName("Join")
							.build();
	}
	
	/**
	 * 
	 * @param joinDef
	 * @param leftTable
	 * @return
	 * JoinStatement
	 */
	public Node buildJoinClause(CommonTree joinDef, Node leftTable)
	{
		/**
		 * Set left part of join clause
		 */
		JoinStatement.Builder builder = new JoinStatement.Builder();
		if(leftTable instanceof Table)
		{
			builder = builder.setLeftTable((Table)leftTable);
		}
		
		/**
		 * Set join type
		 */
		JoinType type = JoinType.valueOf(joinDef.getChild(0).toString());
		builder = builder.setType(type);
		
		/**
		 * Look for right part of join
		 */
		CommonTree tableRefElem = (CommonTree) joinDef.getFirstChildWithType(PLSQLParser.TABLE_REF_ELEMENT);
		Node rightTable = buildTableRefElem(tableRefElem);
		if(rightTable instanceof Table)
		{
			builder = builder.setRightTable((Table)rightTable);
		}
		
		return builder.build();		
	}
	
	public CommonTree deleteChild (CommonTree parent, Tree child)
	{
		int index = child.getChildIndex();
		parent.deleteChild(index);
		return parent;
	}
	
	/**
	 * 
	 * @param func
	 * Not Logical Expression but its Child
	 * @return
	 * Logical Condition
	 */
	public Node buildLogicalExpr(CommonTree func)
	{
		if(func == null)
			return null;
		
		ConditionType type = ConditionType.getConditionType(func.getType());
		
		CommonTree leftTree = (CommonTree) func.getChild(0);
		CommonTree rightTree = (CommonTree) func.getChild(1);
		
		ConditionArgument rightArgument = null;
		
		ConditionArgument leftArgument = (ConditionArgument) buildLogicalArgument(leftTree);
		if(type.equals(ConditionType.BETWEEN) || type.equals(ConditionType.NOT_BETWEEN))
		{
			CommonTree thirdTree = (CommonTree) func.getChild(2);
			return buildBetweenArgument(rightTree, thirdTree);
		}
		else
		{
			rightArgument = (ConditionArgument) buildLogicalArgument(rightTree);
		}

		return new Condition.Builder()
										.setLeftArg(leftArgument)
										.setRightArg(rightArgument)
										.setType(type)
										.build();
	}
	
	public Node buildBetweenArgument(CommonTree leftBound, CommonTree rightBound)
	{
		ArgumentSet set = new ArgumentSet();
		set.addArgument((ConditionArgument) buildLogicalArgument(leftBound));
		set.addArgument((ConditionArgument) buildLogicalArgument(rightBound));
		return set;
	}
	
	public Node buildLogicalArgument(CommonTree argument)
	{
		if(argument == null)
			return null;
		
		switch (argument.getType()) {
		case PLSQLParser.CASCATED_ELEMENT:
			return buildField(argument);
		case PLSQLParser.SQL92_RESERVED_PRIOR:
			return buildPriorClause(argument);
		case PLSQLParser.EXPR:
			return buildExpr(argument);
		default:
			return buildLogicalExpr(argument);
		}
	}
	
	public Node buildPriorClause(CommonTree prior)
	{
		Function priorFunction = new Function();
		priorFunction.setType(FunctionType.PRIOR);
		List<Argument> argList = new ArrayList<Argument>();
		argList.add((Argument)buildExpr(prior));
		priorFunction.setArgList(argList);
		return priorFunction;
	}
	
	public Node buildExpr(CommonTree expr)
	{
		/**
		 * Check whether that is a constant 
		 */		
		CommonTree value = (CommonTree) expr.getChild(0);
		if(value == null)
			return null;
		
		value.deleteChild(0);		
		
		if(value.getChildCount() == 0)
		{
			DataType type = DataType.defineType(value.toString());
			return new Constant(type, value.toString());
		}
		
		/**
		 * Check whether a field
		 */
		if(value.getType() == PLSQLParser.CASCATED_ELEMENT)
			/**
			 * TODO: Routine call
			 */
			return buildField(value);
		
		/**
		 * Check whether that is a case expression
		 */			
		if(value.getType() == PLSQLParser.SQL92_RESERVED_CASE)
			return buildCaseExpr(value);
		
		/**
		 * Check whether that is a standart_function
		 */		
		if(value.getType() == PLSQLParser.STANDARD_FUNCTION)
		{
			return buildStandartFunction(value);
		}
		return null;
	}
	
	public Node buildStandartFunction(CommonTree standartFunc)
	{
		CommonTree function = (CommonTree)standartFunc.getChild(0);
		if(function == null)
			return null;
		
		/**
		 * TODO: check whether string representation of enum is right
		 */
		FunctionType type = FunctionType.valueOf(function.toString());
		
		
		/**
		 * Build arguments
		 */
		CommonTree arguments = (CommonTree)function.getChild(0);
		
		Node arg = null;
		List<Argument> listOfArgsClause = new ArrayList<Argument>();
		
		if(arguments != null)
		{
			List<CommonTree> listOfArgs = arguments.getChildren();
			
			for(CommonTree argument : listOfArgs)
			{
				listOfArgsClause.add((Argument)buildExpr(argument)); 
			}
		}
		
		Function functionClause = new Function(type, listOfArgsClause);
		
		/**
		 * Check whether that is an analytic function 
		 */
		CommonTree over = (CommonTree)function.getChild(1);
		if(over != null && over.getType() == PLSQLParser.OVER_VK)
		{
			return buildAnalyticFunction(over, functionClause);
		}
		
		return functionClause;
	}
	
	public Node buildAnalyticFunction(CommonTree over, Node function)
	{
		/**
		 * Build ORDER BY phrase
		 */
		CommonTree orderByClause = (CommonTree)over.getFirstChildWithType(PLSQLParser.SQL92_RESERVED_ORDER);
		List<OrderByClause> orderByList = null;
		if(orderByClause != null)
		{
			orderByList = buildOrderBy(orderByClause);
		}
		
		/**
		 * Build PARTITION BY phrase
		 */
		CommonTree partitionByClause = (CommonTree) over.getFirstChildWithType(PLSQLParser.PARTITION_VK);
		List<Argument> partitionByList = null;
		if(partitionByClause != null)
		{
			partitionByList = buildPartitionBy(partitionByClause);
		}
		
		/**
		 * Build window bounds
		 */
		CommonTree rowsClause = (CommonTree) over.getFirstChildWithType(PLSQLParser.ROWS_VK);
		CommonTree rangeClause = (CommonTree) over.getFirstChildWithType(PLSQLParser.RANGE_VK);
		WindowClause windowClause = null;
		
		if(rowsClause != null)
		{
			ResultUnit resultUnit =ResultUnit.ROWS;
			windowClause = (WindowClause) buildWindowClause(rowsClause, resultUnit);
		}
		else if(rangeClause != null)
		{
			ResultUnit resultUnit =ResultUnit.RANGE;
			windowClause = (WindowClause) buildWindowClause(rangeClause, resultUnit);
		}
		
		return new AnalyticFunction.Builder()
							.setFunction((Argument)function)
							.setOrderByArgs(orderByList)
							.setPartitionByClause(partitionByList)
							.setWindowingClause(windowClause)
							.build();
	}
	
	public Node buildWindowClause(CommonTree rows, ResultUnit resultUnit)
	{
		if(rows == null)
			return null;
		
		CommonTree windowClause = (CommonTree) rows.getChild(0);
		
		CommonTree leftBound = null;
		CommonTree rightBound = null;
		
		if(windowClause ==null)
			return null;
		
		if(windowClause.getType() == PLSQLParser.SQL92_RESERVED_BETWEEN)
		{
			leftBound = (CommonTree) windowClause.getChild(0);			
			rightBound = (CommonTree) windowClause.getChild(1);
		}
		else
		{
			leftBound = windowClause;
		}
			
		WindowBound leftBoundClause = new WindowBound();
		WindowBound rightBoundClause = new WindowBound();
		
		if(leftBound != null)
		{
			switch (leftBound.getType()) {
				case PLSQLParser.UNBOUNDED_VK:
				{
					leftBoundClause.setType(WindowBoundType.UNBOUNDED_PRECEDING);
					break;
				}					
				case PLSQLParser.CURRENT_VK:
				{
					leftBoundClause.setType(WindowBoundType.CURRENT_ROW);
					break;
				}
				case PLSQLParser.PRECEDING_VK:
				{
					leftBoundClause.setType(WindowBoundType.PRECEDING);
					leftBoundClause.setArg((Argument)buildExpr((CommonTree)leftBound.getChild(0)));
					break;
				}
				case PLSQLParser.FOLLOWING_VK:
				{
					leftBoundClause.setType(WindowBoundType.FOLLOWING);
					leftBoundClause.setArg((Argument)buildExpr((CommonTree)leftBound.getChild(0)));
					break;
				}
			default:
				break;
			}
		}
		if(rightBound != null)
		{
			switch (rightBound.getType()) {
				case PLSQLParser.UNBOUNDED_VK:
				{
					rightBoundClause.setType(WindowBoundType.UNBOUNDED_FOLLOWING);
					break;
				}					
				case PLSQLParser.CURRENT_VK:
				{
					rightBoundClause.setType(WindowBoundType.CURRENT_ROW);
					break;
				}
				case PLSQLParser.PRECEDING_VK:
				{
					rightBoundClause.setType(WindowBoundType.PRECEDING);
					rightBoundClause.setArg((Argument)buildExpr((CommonTree)rightBound.getChild(0)));
					break;
				}
				case PLSQLParser.FOLLOWING_VK:
				{
					rightBoundClause.setType(WindowBoundType.FOLLOWING);
					rightBoundClause.setArg((Argument)buildExpr((CommonTree)rightBound.getChild(0)));
					break;
				}
			default:
				break;
			}
		}
		return new WindowClause.Builder()
						.setLeftBound(leftBoundClause)
						.setRightBound(rightBoundClause)
						.setResultUnit(resultUnit)
						.build();
	}
	
	public List<Argument> buildPartitionBy(CommonTree partition)
	{
		List<Argument> partitionByClause = new ArrayList<Argument>();
		List<CommonTree> cascadedElements = partition.getChildren();
		for(CommonTree cascadedElement : cascadedElements)
		{
			if(cascadedElement.getType() == PLSQLParser.CASCATED_ELEMENT)
			{
				partitionByClause.add((Argument)buildField(cascadedElement));
			}
		}

		return null;
	}
	
	public List<OrderByClause> buildOrderBy(CommonTree order)
	{
		/**
		 * TODO: Sublings
		 */
		List<OrderByClause> orderByArgs = new ArrayList<OrderByClause>();
		
		if(order == null)
			return null;
		
		CommonTree elements = (CommonTree) order.getChild(0);
		
		/**
		 * Read all expressions which are used for sorting
		 */
		if(elements != null)
		{
			List<CommonTree> listOfElements = elements.getChildren();
			
			/**
			 * Read each expression
			 */
			for(CommonTree element : listOfElements)
			{
				OrderByClause orderByArg = new OrderByClause();
				List<CommonTree> args = element.getChildren();
				boolean isFirst = true;
				
				if(args != null)
				{
					
					for(CommonTree arg : args)
					{
												
						/**
						 * Read expression
						 */
						if(isFirst)
						{
							isFirst = false;							
							orderByArg.setOrderByArg((Argument)buildExpr(arg));
							continue;
						}
						
						/**
						 * Read sorting params
						 */
						OrderByType type = null;
						switch(arg.getType())
						{
							case PLSQLParser.NULLS_VK:
							{
								break;
							}
							case PLSQLParser.SQL92_RESERVED_ASC:
							{
								type = OrderByType.ASC;
								break;
							}
							case PLSQLParser.SQL92_RESERVED_DESC:
							{
								type = OrderByType.DESC;
								break;
							}
							case PLSQLParser.FIRST_VK:
							{
								type = OrderByType.NULLS_FIRST;
								break;
							}
							case PLSQLParser.LAST_VK:
							{
								type = OrderByType.NULLS_LAST;
								break;
							}
						}						
						orderByArg.addOrderByParams(type);
					}
				}
				
				orderByArgs.add(orderByArg);
			}
		}
		
		return orderByArgs;
	}
	
	public Node buildField(CommonTree cascadedElem)
	{
		/**
		 * LEVEL, CONNECT_BY_ROOT
		 */
		CommonTree element = (CommonTree) cascadedElem.getChild(0);
		if(element != null &&
					element.getType() == PLSQLParser.ANY_ELEMENT)
		{
			if(element.getChildren().size()>1)
			{
				/**
				 * TODO: consider table alias
				 */
				return new Field(element.getChild(1).toString());
			}
			else
			{
				return new Field(element.getChild(0).toString());
			}
		}
			
		return null;
	}
	
	public Node buildCaseExpr(CommonTree caseExpr)
	{
		CaseExpression.Builder builder = new CaseExpression.Builder();
		
		/**
		 * Rewrite search expression
		 * can be null, if there id no search clause in this statement
		 */
		CommonTree searchClause = (CommonTree) caseExpr.getChild(0);
		Node caseField = null;
		
		if(searchClause != null && searchClause.getType() == PLSQLParser.EXPR)
		{
			caseField = buildExpr(searchClause);
		}
		
		/**
		 * Rewrite WHEN ... THEN expression
		 */
		Map <ConditionArgument, Argument> caseWhenClause = new HashMap<ConditionArgument, Argument>(); 
		while(true)
		{
			CommonTree whenClause = (CommonTree) caseExpr.getFirstChildWithType(PLSQLParser.SQL92_RESERVED_WHEN);
			deleteChild(caseExpr, whenClause);
			
			if(whenClause != null && whenClause.getChildCount() > 1)
			{
				CommonTree whenExpr = (CommonTree) whenClause.getChild(0);
				Node whenNode = null;
				
				/**
				 * If WHEN expression wasn't found search for next when expression
				 */
				if(whenExpr == null)
					continue;
				
				switch (whenExpr.getType()) {
					case PLSQLParser.LOGIC_EXPR:
						whenNode = buildLogicalExpr(whenExpr);
						break;
					case PLSQLParser.EXPR:
						whenNode = buildExpr(whenExpr);
						break;
					default:
						break;
				}
				
				CommonTree thenExpr = (CommonTree) whenClause.getChild(1);
				
				/**
				 * If THEN expression wasn't found search for next when clause
				 */
				if(thenExpr == null)
					continue;
				
				Node thenNode = buildExpr(thenExpr);
				
				caseWhenClause.put((ConditionArgument)whenNode, (Argument)thenNode);
			}
			else
				/**
				 * No more WHEN expressions were found. Stop cycle
				 */
				break;
		}	
		
		/**
		 * Rewrite ELSE expression
		 */
		
		CommonTree elseExpr = (CommonTree) caseExpr.getFirstChildWithType(PLSQLParser.SQL92_RESERVED_ELSE);
		Node caseElseClause = buildExpr(elseExpr);
		
		/**
		 * Build the result Node
		 */
		
		return builder.setCaseWhenClause(caseWhenClause)
						.setCaseElseClause((Argument)caseElseClause)
						.setCaseField((Argument)caseField)
						.build();
	}
	
	public Node buildHierarchicalClause (CommonTree hierarchical)
	{
		/**
		 * Build START WITH expression
		 */
		Condition startCondition = null;
		CommonTree start = (CommonTree)hierarchical.getFirstChildWithType(PLSQLParser.PLSQL_RESERVED_START);
		if(start != null)
		{
			startCondition = (Condition)buildLogicalExpr((CommonTree)start.getChild(0));
		}
		
		/**
		 * Build CONNECT BY clause
		 */
		boolean bNocycle = false;
		Condition connectByCondition = null;
		CommonTree connect = (CommonTree)hierarchical.getFirstChildWithType(PLSQLParser.SQL92_RESERVED_CONNECT);
		if (connect != null)
		{
			/**
			 * Check if NOCYCLE flag is on
			 */
			CommonTree nocycle = (CommonTree) connect.getFirstChildWithType(PLSQLParser.NOCYCLE_VK);
			if(nocycle != null)
				bNocycle = true;
			
			/**
			 * Build connect by
			 */
			CommonTree logicalExpr = (CommonTree) connect.getFirstChildWithType(PLSQLParser.LOGIC_EXPR);
			if(logicalExpr != null)
			{
				connectByCondition = (Condition) buildLogicalExpr((CommonTree)logicalExpr.getChild(0));
			}			
		}
		
		return new HierarchicalClause.Builder()
							.setConnectBy(connectByCondition)
							.setNoCycle(bNocycle)
							.setStartWith(startCondition)
							.build();
	}
}
